﻿using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using sqengine.Engine.Common;

namespace sqengine
{
    public class AwardUnlockedEventArgs : EventArgs
    {
        /// <summary>
        /// Gets the Award that was unlocked.
        /// </summary>
        public Award _Award { get; private set; }

        /// <summary>
        /// Creates a new AwardUnlockedEventArgs with the given award.
        /// </summary>
        /// <param name="award">The award that was unlocked.</param>
        public AwardUnlockedEventArgs(Award award) { _Award = award; }
    }

    public class Award
    {
        /// <summary>
        /// Gets or sets the name of the Award.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the asset name for the texture. Each texture will be fit into
        /// a 64x64 area so being square is ideal.
        /// </summary>
        public string TextureAssetName { get; set; }

        /// <summary>
        /// Gets the texture for the Award.
        /// </summary>
        public Texture2D Texture { get; private set; }

        /// <summary>
        /// Loads the texture to represent the award.
        /// </summary>
        /// <param name="content">The ContentManager to use for loading.</param>
        public void LoadTexture(ContentManager content)
        {
            Texture = content.Load<Texture2D>(TextureAssetName);
        }
    }


	/// <summary>
	/// Contains all the awards for the game and manages everything about them. 
	/// Uses the Guide.NotificationPosition when drawing the announcements.
	/// </summary>
	public class AwardsComponent : DrawableGameComponent
	{
		// we keep a list of unlocked awards
		private readonly List<Award> unlockedAwards = new List<Award>();

		// a queue of recently unlocked awards we need to announce
		private readonly Queue<Award> justUnlocked = new Queue<Award>();

		// the length of time spent fading in and out announcements
		private const float AnnouncementFadeLength = .25f;

		// the length of time the announcement is at full opacity on the screen
		private const float AnnouncementLength = 2f;

		// sets the offset (in pixels from the top-left corner) of where to place the
		// award icon relative to the background texture
		private const int IconOffsetX = 11;
		private const int IconOffsetY = 8;

		// sets the location of the center of the award name in the announcement
		private const int TextCenterX = 161;
		private const int TextCenterY = 25;

		// a timer for announcements
		private float announcementTimer;

		// the background to use for the announcement
		private Texture2D announcementBackground;

		// a sound effect to play when a new announcement is going up
		private SoundEffect announcementSound;

		// a SpriteBatch used to draw everything
		private SpriteBatch spriteBatch;

		// the font used for rendering award names over the background
		private SpriteFont announcementFont;

		// whether or not to draw the announcement
		private bool showAnnouncement;

		/// <summary>
		/// Gets the list of all awards in the game.
		/// </summary>
		public List<Award> Awards { get; private set; }
        private AdvancedContent _adcontent;
		/// <summary>
		/// Raised when an award is unlocked.
		/// </summary>
		/// <remarks>
		/// The event is useful for games that unlock special content when awards
		/// are unlocked. This allows games to unlock awards from anywhere and have
		/// a single location listen for the events to respond accordingly.
		/// </remarks>
		public event EventHandler<AwardUnlockedEventArgs> AwardUnlocked;

		/// <summary>
		/// Creates a new AwardsComponent for the given game.
		/// </summary>
		/// <param name="game">The Game in which the component will run.</param>
		public AwardsComponent(Game game, AdvancedContent adcontent)
			: base(game)
		{
			Awards = new List<Award>();

			// we want awards to draw absolutely last so we set the draw order to max
			DrawOrder = int.MaxValue;
            _adcontent = adcontent;
		}

		protected override void LoadContent()
		{
			// load each award's texture
			Awards.ForEach(a => a.LoadTexture(Game.Content));

			// load the announcement texture, sound, and font
            announcementBackground = _adcontent.LoadTexture2D("Textures\\UI\\Awards\\award-background");
            announcementSound = _adcontent.LoadSoundEffect("Textures\\UI\\Awards\\award-sound");
            announcementFont = _adcontent.LoadSpriteFont("Textures\\UI\\Awards\\award-font");

			// create the SpriteBatch
			spriteBatch = new SpriteBatch(GraphicsDevice);
		}

		public override void Update(GameTime gameTime)
		{
			// if there are unlocked awards left to announce
			if (justUnlocked.Count > 0)
			{
                Awards.ForEach(a => a.LoadTexture(Game.Content));

				// if we're not currently showing an announcement
				if (!showAnnouncement)
				{
					// reset the timer and show the announcement
					announcementTimer = 0f;
					showAnnouncement = true;

					// play our sound effect
					announcementSound.Play();
				}

				// otherwise if we are showing an announcement
				else
				{
					// add to the timer
					announcementTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;

					// if the timer is past the announcement period (fade in, on screen, fade out)
					if (announcementTimer >= (AnnouncementFadeLength * 2) + AnnouncementLength)
					{
						// hide the announcement
						showAnnouncement = false;

						// dequeue the award since it has been announced
						justUnlocked.Dequeue();
					}
				}
			}
		}

		public override void Draw(GameTime gameTime)
		{
			// if an announcement is being shown
			if (showAnnouncement)
			{
				// default to full opacity
				float opacity = 1f;

				// figure out if we're fading in or out and set the opacity accordingly
				if (announcementTimer < AnnouncementFadeLength)
					opacity = announcementTimer / AnnouncementFadeLength;
				else if (announcementTimer > AnnouncementFadeLength + AnnouncementLength)
					opacity = 1f - ((announcementTimer - AnnouncementLength - AnnouncementFadeLength) / AnnouncementFadeLength);

				// figure out the bounds based on the texture size and the Guide.NotificationPosition
				Rectangle announcementBounds = CalculateAnnouncementBounds();

				// make the colors
				Color color = new Color(Color.White, opacity);
				Color fontColor = new Color(Color.Black, opacity);

				// start drawing. we use SaveState in case you have 3D rendering so it won't go bad
				spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState);

				// draw the background
				spriteBatch.Draw(announcementBackground, announcementBounds, color);

				// draw the icon
				spriteBatch.Draw(justUnlocked.Peek().Texture, GetIconBounds(announcementBounds), color);

				// draw the award name
				spriteBatch.DrawString(announcementFont, justUnlocked.Peek().Name, GetNamePosition(announcementBounds), fontColor);

				// done drawing
				spriteBatch.End();
			}

			base.Draw(gameTime);
		}

		/// <summary>
		/// Gets the location where to draw the award name.
		/// </summary>
		/// <param name="announcementBounds">The announcement bounds.</param>
		/// <returns>The new Vector2 position for the award name.</returns>
		private Vector2 GetNamePosition(Rectangle announcementBounds)
		{
			// get the center position of the text using our 
			// TextCenterX and TextCenterY offsets
			Vector2 position = new Vector2(
				announcementBounds.X + TextCenterX, 
				announcementBounds.Y + TextCenterY);

			// measure the text we'll be drawing
			Vector2 stringSize = announcementFont.MeasureString(justUnlocked.Peek().Name);

			// subtract half of the size from the position to center it
			position -= stringSize / 2f;

			// return the position, but cast the X and Y to integers to avoid filtering
			return new Vector2((int)position.X, (int)position.Y);
		}

		/// <summary>
		/// Gets the bounds for the award icon.
		/// </summary>
		/// <param name="announcementBounds">The announcement bounds.</param>
		/// <returns>The new Rectangle bounds for the award icon.</returns>
		private Rectangle GetIconBounds(Rectangle announcementBounds)
		{
			// simply offset the announcement bounds position by the icon offsets
			// and set the width and height to 64
			return new Rectangle(
				announcementBounds.X + IconOffsetX, 
				announcementBounds.Y + IconOffsetY, 
				64, 
				64);
		}

		/// <summary>
		/// Calculates the bounds for the announcement based on the Guide.NotificationPosition property
		/// </summary>
		/// <returns>The new Rectangle bounds for the announcement.</returns>
		private Rectangle CalculateAnnouncementBounds()
		{
			// the amount of the screen to buffer on the sides for the safe zone
			const float safeZoneBuffer = .1f;

			// the actual buffer sizes for the width and height
			int widthBuffer = (int)(GraphicsDevice.PresentationParameters.BackBufferWidth * safeZoneBuffer);
			int heightBuffer = (int)(GraphicsDevice.PresentationParameters.BackBufferHeight * safeZoneBuffer);

			// start with a basic rectangle the size of our texture
			Rectangle announcementBounds = new Rectangle(0, 0, announcementBackground.Width, announcementBackground.Height);

			// figure out all six of our values now to make our switch statement cleaner
			int topY = heightBuffer;
			int centerY = GraphicsDevice.PresentationParameters.BackBufferHeight / 2 - announcementBounds.Height / 2;
			int bottomY = GraphicsDevice.PresentationParameters.BackBufferHeight - heightBuffer - announcementBounds.Height;

			int leftX = widthBuffer;
			int centerX = GraphicsDevice.PresentationParameters.BackBufferWidth / 2 - announcementBounds.Width / 2;
			int rightX = GraphicsDevice.PresentationParameters.BackBufferWidth - widthBuffer - announcementBounds.Width;

			// set the bounds X and Y based on the notification position
			switch (Guide.NotificationPosition)
			{
				case NotificationPosition.BottomCenter:
					announcementBounds.X = centerX;
					announcementBounds.Y = bottomY;
					break;
				case NotificationPosition.BottomLeft:
					announcementBounds.X = leftX;
					announcementBounds.Y = bottomY;
					break;
				case NotificationPosition.BottomRight:
					announcementBounds.X = rightX;
					announcementBounds.Y = bottomY;
					break;
				case NotificationPosition.Center:
					announcementBounds.X = centerX;
					announcementBounds.Y = centerY;
					break;
				case NotificationPosition.CenterLeft:
					announcementBounds.X = leftX;
					announcementBounds.Y = centerY;
					break;
				case NotificationPosition.CenterRight:
					announcementBounds.X = rightX;
					announcementBounds.Y = centerY;
					break;
				case NotificationPosition.TopCenter:
					announcementBounds.X = centerX;
					announcementBounds.Y = topY;
					break;
				case NotificationPosition.TopLeft:
					announcementBounds.X = leftX;
					announcementBounds.Y = topY;
					break;
				case NotificationPosition.TopRight:
					announcementBounds.X = rightX;
					announcementBounds.Y = topY;
					break;
			}

			return announcementBounds;
		}

		/// <summary>
		/// Retrieves an award by name.
		/// </summary>
		/// <param name="name">The name of the award to retrieve.</param>
		/// <returns>The Award instance if a match was found, null otherwise.</returns>
		public Award GetAwardByName(string name)
		{
			// fine the award based on name.
			return Awards.Find(a => a.Name == name);
		}

		/// <summary>
		/// Determines if a given award has been unlocked.
		/// </summary>
		/// <param name="award"></param>
		/// <returns></returns>
		public bool IsAwardUnlocked(Award award)
		{
			// simple check to see if the award is in our unlocked list
			return unlockedAwards.Contains(award);
		}

		/// <summary>
		/// Unlocks an award.
		/// </summary>
		/// <param name="award">The award to unlock.</param>
		public void UnlockAward(Award award)
		{
			// make sure the award is in our list and is not already unlocked
			if (Awards.Contains(award) && !IsAwardUnlocked(award))
			{
				// add it to the unlocked awards list
				unlockedAwards.Add(award);

				// add it to the just unlocked queue
				justUnlocked.Enqueue(award);

				// invoke the event
				if (AwardUnlocked != null)
					AwardUnlocked(this, new AwardUnlockedEventArgs(award));
			}
		}

		/// <summary>
		/// Saves a list of the unlocked award names to a file. This is not the best
		/// route on Windows where a user could easily edit the file, but on Xbox
		/// it is sufficient given that the hard drive is not easily accessible.
		/// </summary>
		/// <param name="file">The file path to which the data is saved.</param>
		public void SaveUnlockedAwards(string file)
		{
			// delete the file if it exists
			if (File.Exists(file))
				File.Delete(file);

			// use a StreamWriter to write the name of each unlocked award to the file
			using (StreamWriter writer = new StreamWriter(file))
			{
				unlockedAwards.ForEach(a => writer.WriteLine(a.Name));
			}
		}

		/// <summary>
		/// Loads a list of unlocked award names from a file.
		/// </summary>
		/// <param name="file">The file path from which the data is read.</param>
		public void LoadUnlockedAwards(string file)
		{
			// clear the list of unlocked awards
			unlockedAwards.Clear();

			// use a StreamReader to read in the data
			using (StreamReader reader = new StreamReader(file))
			{
				// loop while we're not at the end of the stream
				while (!reader.EndOfStream)
				{
					// get an award by name where each line is an award
					Award award = GetAwardByName(reader.ReadLine());

					// if the award is not null, add it to our unlocked awards list
					if (award != null)
						unlockedAwards.Add(award);
				}
			}
		}
	}
}